<?php

	/*
		XSLFeed class for procing FEEDS with XSL
		Altman, Ryan 2008 Summer
	*/

	libxml_use_internal_errors(true);

	class DOMDocumentExceptor extends DOMDocument
	{
		public function load( $xml, $version=false )
		{
			if( !@parent::load( $xml, $version ) )
			{
				self::handleError();
			}
		}
		public function loadXML( $xml, $options=false )
		{
			if( !@parent::loadXML( $xml, $options ) )
			{
				self::handleError();
			}
		}

		private static function handleError()
		{
			$errors = libxml_get_errors();

			$bubble = "";
			foreach( $errors as $error )
			{
				$bubble .= self::formatXmlError($error);
			}

			libxml_clear_errors();

			throw new Exception( "libxml Exception" . $bubble );
		}
		private static function formatXmlError( $error )
		{
			$rv = "\n";
			switch( $error->level )
			{
				case LIBXML_ERR_WARNING:
					$rv .= "Warning $error->code: ";
					break;
				case LIBXML_ERR_ERROR:
					$rv .= "Error $error->code: ";
					break;
				case LIBXML_ERR_FATAL:
					$rv .= "Fatal Error $error->code: ";
					break;
			}

			$rv .= ", Line: $error->line, Column: $error->column";

			if( $error->file )
			{
				$rv .= ", File: $error->file";
			}

			return $rv;
		}
	}
	
	class IO
	{
	    public function curl( $resource, $method="get", $fields=null, $auth=false )
        {
            if( strcasecmp($method,"get")==0 && is_array($fields) )
            {
                $resource .= "?";
                foreach( $fields as $k=>$v )
                    $resource .= $k ."=". urlencode($v) ."&";
            }
                
            $csess = curl_init( $resource );

            curl_setopt( $csess, CURLOPT_HEADER, 0 );
            curl_setopt( $csess, CURLOPT_RETURNTRANSFER, 1 );

            if( $auth )
            {
                curl_setopt( $csess, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
                curl_setopt( $csess, CURLOPT_USERPWD, "{$auth}");
            }

            if( strcasecmp($method,"post")==0 )
            {
                curl_setopt( $csess, CURLOPT_POST, 1 );
                curl_setopt( $csess, CURLOPT_POSTFIELDS, $fields );
            }

            $resp = curl_exec( $csess );

            curl_close( $csess );

            return $resp;
        }
	}


	class XSLFeed
	{
		public static $processor;
		private $uri;
		private $file;
		private $expire;
		private $doc;
		private $xsl;

		function __construct( $uri, $file, $expire=300, $xsl=false )
		{

			$this->uri = $uri;
			$this->file = $file;
			$this->expire = $expire;
			$this->xsl = $xsl;

		}

		public function output($xsl=false,$text=false)
		{
			$rss = $this->file;
			$expire = $this->expire;
			$over = false;

			if( $text )
			{
				$uncachedText = self::uncache($rss . '.html', $expire, false);
				if( $uncachedText ) return $uncachedText;
				$over = true;
			}

			if( $over || !self::uncache($rss . '.html', $expire)  )
			{
				try
				{

					$doc = new DOMDocumentExceptor();
					$valid=true;

					//
					// try reserved XML
					if( file_exists($rss) && (time() - filemtime($rss))<$expire && filesize($rss)>5)
					{
						echo "\n\n\t\t<!-- cached xml -->\n\n";
						$doc->load( $rss );

					}
					else if( $xml= self::getXmlFromCurl( $this->uri, $rss ) )
					{

						echo "\n\n\t\t<!-- wget xml -->\n\n";
						$doc->loadXML( $xml );

						//
						// save new xml
						// self::cache( $doc, $rss );
					}
					else
					{
						echo "<!-- Feed not loaded -->";
						$valid = false;
					}
				}
				catch( Exeception $e )
				{
					echo $e->getMessage();
					$valid=false;
				}


				if( $valid && $doc )
				{
					$dxsl = strlen($xsl) ? $xsl : ($this->xsl ? $this->xsl : false);
					
					try
					{
						$transformed = self::transform( $doc, $dxsl );
						self::cache( $transformed, $rss .".html" );
						if( $text )
						{
							return $transformed;
						}
						else
						{
							echo $transformed;
						}
					}
					catch( Exception $e )
					{
						//
						echo $e->getMessage();
					}
				}
				else
				{
					// retry?
					// uncache old
					if( $text )
					{
						return self::uncache($rss . ".html", false, false);
					}
					else
					{
						self::uncache($rss . ".html", false);
					}
				}
			}
		}

		private static function transform( &$doc, $xsl=false )
		{
			if( $xsl )
			{
				$xsld = New DOMDocument();
				$xsld->load( $xsl );
				self::$processor->importStylesheet( $xsld );

				ob_start();

				if( ! self::$processor->transformToURI($doc,"php://output") )
					throw new Exception( "Error in XSL transformation" );

				$transform = ob_get_contents();
				ob_end_clean();

				return $transform;
			}
			else
			{
				throw new Exception( "Missing XSL for transform in XSLFeed" );
			}
		}

		private static function getXmlFromWget( $uri, $rssfile )
		{
			self::wget( $uri, $rssfile );
			$rv = file_get_contents( $rssfile );
			return strlen($rv)>5?$rv:false;
		}

		private static function getXmlFromCurl( $uri )
		{
			$resp = IO::curl( $uri );
			return strlen($resp)>5?$resp:false;
		}
		
		private static function wget($uri, $dest)
		{
			return exec( "wget -O $dest '$uri'" );
		}

		private static function uncache($fn, $exp=false, $output=true)
		{
			//echo "\n<!--\n uncaching ". time() .":". filemtime($fn) .":". (time()-filemtime($fn)) . "\n-->\n";
			if( file_exists($fn) && ($exp===false || time()-filemtime($fn)<$exp) )
			{ // uncache
				echo "\n<!-- uncaching -->\n";
				if( $output )
				{
					echo file_get_contents($fn);
					return true;
				}
				else
				{
					return file_get_contents($fn);
				}
			}
			return false;
		}
		private static function cache( $o, $fn )
		{
			$tmp = $fn .".". microtime(true); 

			if( is_object($o) )
			{
				switch( get_class($o) )
				{
					case 'DOMDocument':
					case 'DOMDocumentExceptor':
						$o->save($tmp); 
						break;
					default:
						return 0;
				}
			}
			else
			{
				file_put_contents( $tmp, $o );
			}

			rename( $tmp, $fn );
		}

		public static function init()
		{
			self::$processor = new XSLTProcessor();
			self::$processor->registerPHPFunctions();
		}
	}

	XSLFeed::init();

?>
